package newcrunch.codeanalysis;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import javax.swing.JEditorPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMD.ProgressMonitor;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleSetFactory;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.util.IOUtil;
import net.sourceforge.pmd.util.datasource.DataSource;
import newcrunch.gui.RightTab;
import newcrunch.util.Logging;
import newcrunch.util.Misc;
import newcrunch.util.PropertyLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class PMDPlugin 
{
    private static String rulesets = "newcrunch/codeanalysis/pmd.xml";
    private static String reportFormat = "xml";
    private static DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    private static DocumentBuilder docBuilder;
    
    static
    {
        try {
            rulesets = PropertyLoader.getRulesProperty("pmd");
            docBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (Exception exp) {
            Logging.log(exp);
        }
    }
    
//    public static InfoNode test() throws IOException, ParserConfigurationException, SAXException
//    {
//        FileInputStream fs = new FileInputStream("/home/ccadm/NetBeansProjects/NewCrunch/src/test/java/pmd-tests");
//        Scanner sc = new Scanner(fs);
//        StringBuilder code = new StringBuilder();
//        while (sc.hasNextLine())
//        {
//            code.append(sc.nextLine() + "\n");
//        }
//        fs.close();
//        
//        JEditorPane editor = new JEditorPane();
//        editor.setText(code.toString());
//        return PMDPlugin.runPMD("test file", editor);
//    }
    
    public static InfoNode runPMD(RightTab rightTab, ProgressMonitor monitor) throws IOException, 
                                        ParserConfigurationException, SAXException
    {
        String filename = rightTab.getFilename();
        JEditorPane editor = rightTab.getEditor();
        
        InfoNode pmdNode = new InfoNode("PMD");
        
        if ("".equals(editor.getText()))
            return pmdNode;
        
        String result = process(filename, editor.getText(), monitor);
        
        Document doc = docBuilder.parse(new ByteArrayInputStream(result.getBytes()));
        
        Element root = doc.getDocumentElement();
        
        Element firstChild = PluginUtil.getFirstElement(root);
        
        if (firstChild == null)
            return pmdNode;
        
        HashMap<String, InfoNode> headings = new HashMap<String, InfoNode>();
        
        if ("file".equals(firstChild.getTagName()))
        {
            NodeList list = firstChild.getChildNodes();
            for (int i = 0; i < list.getLength(); i++)
            {
                //group the violations in terms of their rules.
                if (list.item(i) instanceof Element)
                {
                    Element currElement = (Element)list.item(i);
                    String ruleName = currElement.getAttribute("rule");
                    
                    InfoNode headingNode = null;
                    
                    if (headings.containsKey(ruleName))
                        headingNode = headings.get(ruleName);
                    else
                    {
                        headingNode = new InfoNode(ruleName);
                        headings.put(ruleName, headingNode);
                        
                        //adds the description
                        InfoNode descNode = new InfoNode("Description");
                        descNode.add(new InfoNode(currElement.getTextContent()));
                        headingNode.add(descNode);                
                    }
                    
                    headingNode.add(generateNode(currElement, editor));
                }
            }
            
            String[] rules = headings.keySet().toArray(new String[headings.keySet().size()]);
            Arrays.sort(rules);
            for (String rule : rules)
                pmdNode.add(headings.get(rule));
        }
        else
        {
            pmdNode.add(new InfoNode(firstChild.getAttribute("msg")));
        }
        
        return pmdNode;
    }
    
    private static InfoNode generateNode(Element element, JEditorPane editor)
    {
        int startRow = Integer.parseInt(element.getAttribute("beginline"));
        int startCol = Integer.parseInt(element.getAttribute("begincolumn"));
        int endRow = Integer.parseInt(element.getAttribute("endline"));
        int endCol = Integer.parseInt(element.getAttribute("endcolumn"));
        
        String firstLine = editor.getText().substring(
                            Misc.rowToPosition(editor, startRow - 1), 
                            Misc.rowToPosition(editor, startRow));
        
        int startPos = Misc.rowToPosition(editor, startRow - 1) + 
                PluginUtil.dispIndexToStrIndex(firstLine, startCol - 1);
        
        String lastLine = editor.getText().substring(
                Misc.rowToPosition(editor, endRow - 1), 
                Misc.rowToPosition(editor, endRow));
        int endPos = Misc.rowToPosition(editor, endRow - 1) + PluginUtil.dispIndexToStrIndex(lastLine, endCol);
        
        int firstRowStart = PluginUtil.dispIndexToStrIndex(firstLine, startCol - 1);
        int firstRowEnd = (endRow > startRow) ? -1 : PluginUtil.dispIndexToStrIndex(firstLine, endCol);
        String display = formatDisplay(firstLine, firstRowStart, firstRowEnd) + 
                "    [" + startRow + ":" + startCol + "]";
        
        return new InfoNode(display, startPos, endPos);
    }
    
    /**
     * Boldens the targeted code in the display.
     * Use the length of the string as last character if end < 0 or end > start.
     * @param input
     * @param start
     * @param end
     * @return 
     */
    private static String formatDisplay(String input, int start, int end)
    {        
        if (end < 0 || end < start)
            end = input.length();
        
        String output = input.substring(0, start) + "<b>" +  input.substring(start, end) + "</b>" + input.substring(end);
        output = output.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        output = output.replaceAll("&lt;b&gt;", "<b>").replaceAll("&lt;/b&gt;", "</b>");
        
        return "<html>" + output + "</html>";
    }
    
    public static String process(String filename, String code, ProgressMonitor monitor) throws IOException
    {     
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        PrintStream oldPrintStream = System.out;
        System.setOut(new PrintStream(bout));
        
        RuleSetFactory ruleSetFactory = new RuleSetFactory();
        List<DataSource> files = new ArrayList<DataSource>();
        DataSource src = new PMDSourceFile(filename, code);
        files.add(src);
        
        PMDConfiguration configuration = new PMDConfiguration();
        configuration.setReportFormat(reportFormat);
        configuration.setRuleSets(rulesets);
        
        Renderer renderer = configuration.createRenderer();
        List<Renderer> renderers = new LinkedList<Renderer>();
        renderers.add(renderer);

        renderer.setWriter(IOUtil.createWriter(configuration.getReportFile()));
        renderer.start();

        RuleContext ctx = new RuleContext();

        PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers, monitor);
        
        renderer.end();
        renderer.flush();        
        
        bout.close();        
        System.setOut(oldPrintStream);
        
        return bout.toString();
    }
}
